package com.dodo.blog.server.impl;

import com.comvai.services.persistence.LowLevel;
import com.comvai.services.persistence.PersistenceManager;
import com.comvai.services.persistence.criteria.Criteria;
import com.comvai.services.persistence.criteria.Order;
import com.comvai.services.persistence.criteria.Restrictions;
import com.dodo.blog.exception.UserAlreadyRated;
import com.dodo.blog.model.Article;
import com.dodo.blog.model.Category;
import com.dodo.blog.model.Rate;
import com.dodo.blog.model.Tag;
import com.dodo.blog.request.ArticlesRequest;
import com.dodo.blog.server.ArticleService;
import com.dodo.blog.server.CategoryService;
import com.dodo.blog.server.TagService;
import org.apache.commons.lang.StringUtils;

import javax.inject.Inject;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.List;

/**
 * @author <a href="mailto:pohorelec@comvai.com">Jozef Pohorelec</a>
 */
public class ArticleServiceBean
        extends AbstractServiceBean
        implements ArticleService
{
    private final TagService tagService;

    private final CategoryService categoryService;

    @Inject
    public ArticleServiceBean( @LowLevel PersistenceManager datastore, CategoryService categoryService, TagService tagService )
    {
        super( datastore );
        this.categoryService = categoryService;
        this.tagService = tagService;
    }

    @Override
    public void saveArticle( Article article )
    {
        save( article );
    }

    @Override
    public Article getArticleById( Long articleId )
    {
        return datastore.find( Article.class, articleId );
    }

    @Override
    public List<Article> getArticles( ArticlesRequest request )
    {
        if ( request.getTag() != null )
        {
            List<Article> tagList = new ArrayList<Article>();
            Tag tag = tagService.getTagByNormalizedName( request.getTag() );

            // return empty result if tag was not found
            if ( tag == null )
            {
                return tagList;
            }

            tagList.addAll( getTagList( "tag_1", request, tag ) );
            tagList.addAll( getTagList( "tag_2", request, tag ) );
            tagList.addAll( getTagList( "tag_3", request, tag ) );
            tagList.addAll( getTagList( "tag_4", request, tag ) );
            tagList.addAll( getTagList( "tag_5", request, tag ) );

            Collections.sort( tagList, new Comparator<Article>()
            {
                @Override
                public int compare( Article o1, Article o2 )
                {
                    return o2.getCreatedDate().compareTo( o1.getCreatedDate() );
                }
            } );

            if ( tagList.size() > request.getMaxRows() )
            {
                return tagList.subList( 0, request.getMaxRows() );
            }
            else
            {
                return tagList;
            }
        }
        else
        {
            Criteria<Article> criteria = Criteria.create( Article.class );
            processCriteria( criteria, request );

            if ( request.getCategory() != null )
            {
                Category category = categoryService.getCategoryByNormalizedName( request.getCategory() );

                // return empty result if category was not found
                if ( category == null )
                {
                    return new ArrayList<Article>();
                }

                criteria.addCriteria( Restrictions.eq( "category", category.getId() ) );
            }

            if ( request.isPublished() != null )
            {
                criteria.addCriteria( Restrictions.eq( "published", true ) );
            }

            if ( !StringUtils.isBlank( request.getYear() ) )
            {
                criteria.addCriteria( Restrictions.eq( "year", request.getYear() ) );
            }

            if ( !StringUtils.isBlank( request.getMonth() ) )
            {
                criteria.addCriteria( Restrictions.eq( "month", request.getMonth() ) );
            }

            if ( !StringUtils.isBlank( request.getDay() ) )
            {
                criteria.addCriteria( Restrictions.eq( "day", request.getDay() ) );
            }

            return datastore.list( criteria );
        }
    }

    @Override
    public List<Article> getMostViewedArticles()
    {
        Criteria<Article> criteria = Criteria.create( Article.class );
        criteria.addCriteria( Restrictions.eq( "published", true ) );
        criteria.addOrderRule( "viewed", Order.DESC );
        criteria.setMaxResults( 5 );

        return datastore.list( criteria );
    }

    @Override
    public List<Article> getBestRatedArticles()
    {
        Criteria<Article> criteria = Criteria.create( Article.class );
        criteria.addCriteria( Restrictions.eq( "published", true ) );
        criteria.addOrderRule( "rate", Order.DESC );
        criteria.setMaxResults( 5 );

        return datastore.list( criteria );
    }

    @Override
    public List<Article> getRecentArticles()
    {
        Criteria<Article> criteria = Criteria.create( Article.class );
        criteria.addCriteria( Restrictions.eq( "published", true ) );
        criteria.addOrderRule( "publishedDate", Order.DESC );
        criteria.setMaxResults( 5 );

        return datastore.list( criteria );
    }

    private List<Article> getTagList( String tagProperty, ArticlesRequest request, Tag tag )
    {
        Criteria<Article> criteria = Criteria.create( Article.class );
        processCriteria( criteria, request );
        criteria.addCriteria( Restrictions.eq( tagProperty, tag.getId() ) );
        if ( request.isPublished() != null )
        {
            criteria.addCriteria( Restrictions.eq( "published", true ) );
        }

        return datastore.list( criteria );
    }

    @Override
    public void deleteArticle( Long articleId )
    {
        datastore.delete( Article.class, articleId );
    }

    @Override
    public Article rate( Long articleId, Long accountId, Long rate ) throws UserAlreadyRated
    {
        if ( isArticleRated( articleId, accountId ) )
        {
            throw new UserAlreadyRated();
        }

        Article article = getArticleById( articleId );
        if ( article != null && accountId != null )
        {
            // rate article
            article.incrementRate( rate );
            save( article );

            // create Rate entity that holds info whether user already voted for this article or not
            save( new Rate( articleId, accountId ) );
        }

        return article;
    }

    @Override
    public boolean isArticleRated( Long articleId, Long accountId )
    {
        Criteria<Rate> criteria = Criteria.create( Rate.class );
        criteria.addCriteria( Restrictions.eq( "articleId", articleId ) );
        criteria.addCriteria( Restrictions.eq( "accountId", accountId ) );

        return datastore.list( criteria ).size() > 0;
    }

    @Override
    public void publishArticle( Long articleId )
    {
        Article article = getArticleById( articleId );

        article.setPublished( true );
        article.setPublishedDate( new Date() );
        saveArticle( article );
    }

    @Override
    public void hideArticle( Long articleId )
    {
        Article article = getArticleById( articleId );

        article.setPublished( false );
        article.setPublishedDate( null );
        saveArticle( article );
    }
}